/******************************************************************
 * Copyright @ 2023 广州牛师帮科技文化有限公司. All rights reserverd.
 * @file   src\component\Display\index.tsx
 * @author 朱剑豪
 * @date   2023/10/24 15:26
 * @desc   控制组件是否出现在虚拟dom树中
 ******************************************************************/

import { useEventListener } from "@nsb/common-tools";
import { Col, ColProps, Row, RowProps, Typography } from "antd";
import { BlockProps } from "antd/es/typography/Base";
import classnames from "classnames";
import React, { CSSProperties, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import styles from "./style.module.scss";

interface ShowProps extends React.PropsWithChildren {
  /**
   * 控制内部元素的显示
   *
   * @type {*} 默认: true
   */
  yes: any;

  inner?: () => React.ReactNode;
}

/**
 * 控制组件是否出现在虚拟dom树中
 *
 * @export
 * @param {ShowProps} props
 * @return {(React.ReactElement | null)}
 */
export function Show(props: ShowProps): React.ReactElement | null {
  const { yes = true, inner, children = inner?.() } = props
  return yes ? <>{children}</> : null
}
export type AnimDirection = 'vertical' | 'horizontal' | 'both' | 'none'

const css_transition_time = 200
const css_transition_txt = `min-width ${css_transition_time}ms, min-height ${css_transition_time}ms, width ${css_transition_time}ms, height ${css_transition_time}ms, opacity ${css_transition_time}ms`

const gone_styles: Record<AnimDirection, CSSProperties> = {
  vertical: { minHeight: 0, minWidth: void 0, width: void 0, height: 0, opacity: 0, transition: css_transition_txt },
  horizontal: { minHeight: void 0, minWidth: 0, height: void 0, width: 0, opacity: 0, transition: css_transition_txt },
  both: { minHeight: 0, minWidth: 0, height: 0, width: 0, opacity: 0, transition: css_transition_txt },
  none: { opacity: 0, transition: css_transition_txt }
}
const start_styles: Record<AnimDirection, CSSProperties> = {
  vertical: {
    transition: css_transition_txt,
    height: 'auto',
    opacity: 1,
    width: void 0,
  },
  horizontal: {
    transition: css_transition_txt,
    opacity: 1,
    width: 'auto',
    height: void 0,
  },
  both: {
    transition: css_transition_txt,
    opacity: 1,
    width: 'auto',
    height: 'auto'
  },
  none: {
    transition: css_transition_txt,
    opacity: 1
  }
}

export interface IShowEleProps<T extends HTMLElement = HTMLElement> extends ShowProps {
  tag: string | React.ForwardRefExoticComponent<ColProps & React.RefAttributes<T>>,
  /**
   * 是否连同自身div一起控制
   * 当withSelf==true && yes==false 时，原生dom树中将不会存在此div
   * 当withSelf==false && yes==false 时，原生dom树中存在此div，但此div无内容
   *
   * @type {?boolean} 不传将默认为true
   */
  withSelf?: boolean;
  withAnim?: boolean;
  animDirection?: AnimDirection

  /**
   * 当为true，内容不会不显示，而是Div本身display none
   * 默认false
   *
   * @type {?boolean}
   */
  justGone?: boolean;
  className?: string;
  style?: CSSProperties;
}
Show.Ele = function <T extends HTMLElement = HTMLElement>(props: IShowEleProps<T>) {
  const {
    tag,
    yes,
    children,
    withSelf = true,
    withAnim = false,
    className,
    style,
    animDirection = 'vertical',
    justGone = false,
    ...ele_props
  } = props

  const [anim_style, set_anim_style] = useState<React.CSSProperties>(() => {
    if (!withAnim) return {}
    return (yes ? start_styles : gone_styles)[animDirection]
  })
  const classNames = classnames(styles.show_div, className)
  const ref_ele = useRef<T>(null)
  const [gone, set_gone] = useState(!yes);
  const final_style = { ...anim_style, ...style }
  if (gone && final_style) {
    final_style.display = 'none'
  }
  const update_style = useCallback(() => {

    if (!withAnim) return;
    if (!ref_ele.current) return;

    const { children } = ref_ele.current;
    const len = children.length;
    let l = 0;
    let t = 0;
    let r = 0;
    let b = -1;
    for (let i = 0; i < len; ++i) {
      const child = children.item(i);
      if (!child) continue;
      const { x, y, width: w, height: h } = child.getBoundingClientRect();
      if (-1 === b) {
        l = x;
        t = y;
        r = x + w;
        b = y + h;
      } else {
        l = Math.min(x, l)
        t = Math.min(y, t)
        r = Math.max(x + w, r)
        b = Math.max(y + h, b)
      }
    }
    const { paddingTop, paddingBottom, paddingLeft, paddingRight } = getComputedStyle(ref_ele.current);

    const height = b - t + (parseFloat(paddingTop) || 0) + (parseFloat(paddingBottom) || 0);
    const width = r - l + (parseFloat(paddingLeft) || 0) + (parseFloat(paddingRight) || 0);
    const style = start_styles[animDirection];
    const next_styles: Record<AnimDirection, CSSProperties> = {
      vertical: { ...style, height: height, minHeight: height },
      horizontal: { ...style, width: width, minWidth: width },
      both: { ...style, width: width, height: height, minWidth: width, minHeight: height },
      none: { ...style }
    }
    const next_style = next_styles[animDirection]
    set_anim_style(next_style);
    const tid = setTimeout(() => {
      set_anim_style({ ...next_style, width: 'auto', height: 'auto' })
    }, 300)
    return () => clearTimeout(tid)
  }, [withAnim])

  useLayoutEffect(() => {
    if (gone) return;
    update_style();
    /*
     FIXME: 
        该死的，一些内部元素首次计算高度并不正确。稍微延迟一点再算一次，确保高度正确。
        这方法很蠢，但看起来没有问题。
     -Gim
     */
    const tid = setTimeout(() => update_style(), 10);
    return () => clearTimeout(tid);
  }, [gone, update_style])

  useEventListener(window, 'resize', update_style, void 0, !gone)

  useEffect(() => {
    if (!withAnim) {
      set_anim_style({})
      set_gone(!yes)
      return;
    }
    if (yes) {
      set_gone(false)
      return;
    }
    set_anim_style(gone_styles[animDirection])
    const tid = setTimeout(() => set_gone(true), css_transition_time)
    return () => clearTimeout(tid)
  }, [yes])

  const ob = useMemo(() => new MutationObserver(() => update_style()), [])

  useEffect(() => {
    if (!ref_ele.current) return
    ob.observe(ref_ele.current, { subtree: true, childList: true })
  }, [ref_ele.current])

  return justGone ? (
    React.createElement(tag, { ...ele_props, ref: ref_ele, children, className: classNames, style: final_style })
  ) : withSelf ? (
    <Show yes={!gone}>
      {React.createElement(tag, { ...ele_props, ref: ref_ele as any, children, className: classNames, style: final_style })}
    </Show>
  ) : (
    React.createElement(tag, {
      ...ele_props, ref: ref_ele, children: <Show yes={!gone}>
        {children}
      </Show>,
      className: classNames, style: final_style
    })
  )
}

export interface IShowDivProps extends React.HTMLAttributes<HTMLDivElement>, Omit<IShowEleProps, 'tag'> {
}
Show.Div = (props: IShowDivProps) => <Show.Ele tag='div' {...props} />

export interface IShowSpanProps extends React.HTMLAttributes<HTMLSpanElement>, Omit<IShowEleProps, 'tag'> {
}

Show.Span = (props: IShowSpanProps) => <Show.Ele tag='span' {...props} />

export interface IShowColProps extends ColProps, Omit<IShowEleProps, 'tag'> {
}

Show.Col = (props: IShowColProps) => <Show.Ele tag={Col} {...props} />


export interface IShowRowProps extends RowProps, Omit<IShowEleProps, 'tag'> {
}

Show.Row = (props: IShowRowProps) => <Show.Ele tag={Row} {...props} />

export interface IShowTextProps extends BlockProps, Omit<IShowEleProps, 'tag'> {
}

Show.Text = (props: IShowTextProps) => <Show.Ele tag={Typography.Text} {...props} />

interface GoneProps {
  /**
   * 控制内部元素的隐藏
   * 
   * single属性为true的情况下，当yes布尔判定为true时，隐藏内部第一个元素，否则隐藏第二个元素。
   * single属性为false的情况下, 当yes布尔判定为true时，隐藏内部全部的元素，否则不隐藏。
   * 
   * 默认为false
   */
  yes: any;

  /**
   * 控制内部元素的选择模式
   * 
   * 默认为false
   */
  single?: boolean;

  /**
   * 内部元素
   */
  children: React.ReactNode;
}

/**
 * 控制组件是否出“不现在”虚拟dom树中
 *
 * @export
 * @param {GoneProps} props
 * @return {(React.ReactElement | null)}  {(React.ReactElement | null)}
 */
export function Gone(props: GoneProps): React.ReactElement | null {
  const { single, yes, children } = props
  if (!single || !Array.isArray(children))
    return !yes ? <>{children}</> : null
  return !yes ? children[0] : children[1]
}
